Optimizing Graphics Performance
Manual     Reference     Scripting   
Unity Manual > Advanced > Optimizing Graphics Performance

Optimizing Graphics Performance

Desktop

Making your game run smoothly is critical to its success. Thankfully Unity is there for you! We have spent a lot of time and energy making Unity iOS run fast on a wide variety of hardware. Below are some simple guidelines to maximizing the speed of your game.

In Summary - Combine, Combine, Combine

  • If you care about performance, combine meshes.
  • If you care about performance, make sure all of your combined meshes also share the same material and texture.
  • The Profiler and Rendering Statistics window are very helpful!

In Detail:

Modern graphics cards are really good at pushing a lot of polygons, but they have quite a bit of overhead for every batch that you submit to the graphics card. So if you have a 100-triangle object it is going to be just as expensive to render as a 1500-triangle object. The "sweet spot" for optimal rendering performance is somewhere around 1500-4000 triangles per mesh.

You only pay a rendering cost for objects that have a Mesh Renderer attached. And you only pay for those that are within the view frustum. There is no rendering cost from having a lot of empty GameObjects in your scene.

  • The best way to improve rendering performance is to combine objects together so each mesh has around 1500 or more triangles and uses only one Material for the entire mesh.

It is important to understand that just combining two objects which don't share a material does not give you any performance increase at all. If you want to combine effectively, you need to make sure your mesh uses only one material after you have combined it.

There is, however, one thing to be aware of when combining objects: if you use a lot of small lights in your scene, it might make sense to combine only objects that are close to each other.

The rendering cost for a mesh that has multiple materials is the same as having multiple renderers for each material. The most common reason why you have multiple materials is that two meshes don't share the same textures. So, if you want to optimize rendering performance, you need to make sure that the objects you combine share textures.

  • Unity is very good at pushing lots of polygons. Unity uploads all geometry to the graphics card for good cache utilization and optimal data alignment.
  • You simply have to make sure that the graphics card doesn't have to handle large numbers of batches.
  • If you use Forward rendering path, the number of Pixel Lights affecting an object heavily affects performance.

Pixel Lights in Forward Rendering Path

Note: this applies only to Forward rendering path.

If you use pixel lighting, then each GameObject has to be rendered as many times as there are pixel lights that affect the object. If you combine two objects that are very far apart, it might increase the size of the object and now you have a lot of lights affecting this big object. If your objects were separate however, the light won't have to be applied on the part of the mesh which is far away. This can result in rendering the combined mesh as many times as the uncombined mesh (thus not saving anything). For this reason, you should keep GameObjects that are very far away as individual Meshes.

When rendering a mesh, Unity finds all lights surrounding the mesh. It then figures out what lights affect the mesh the most. The QualitySettings are used to modify how many of the lights end up as pixel lights and how many as vertex lights.

Every light calculates its importance based on how far away it is from the mesh and how intense it is.

Some lights are more important than others depending on the game context. For this reason, every light has a Render Mode setting which can be set to Important or Not Important.

Imagine the player's car with head lights driving through the night. The head lights are the most important light in the game. For this reason, the head lights Render Mode should be set to Important.

If you have a light that is not very important and also visually doesn't gain much from being a pixel light, set the lights Render Mode to Not Important. This way, you don't waste rendering performance or lose any visual quality.

Per-Layer Cull Distances

You might want to cull small objects earlier to reduce number of draw calls. For example, small rocks and debris could be made invisible at much smaller distance than large buildings. To do that, put small objects into a separate layer and setup per-layer cull distances using the Camera.layerCullDistances script function.

Shadows

If you are deploying for Desktop platforms then you should pay attention to shadows; shadows are generally expensive. They can add a lot of performance overhead to your game if they are not used correctly. For more details about shadows, please read the Shadows page.

Note: Remember that shadows are not currently supported on iOS or Android devices.

See Also

iOS

If you want to optimize your content for iOS, then it is beneficial for you to learn more about iOS hardware devices.

Alpha-Testing

Contrary to the desktop, alpha-testing (or use of discard / clip operation in pixel shader) is very expensive on iOS. If you can replace your alpha-test shader with alpha-blend, do so. If you absolutely need to use alpha-testing, then you should keep areas of visible alpha-tested pixels to a minimum.

Vertex Performance

Generally you should aim at 40K or less vertices visible per frame when targeting iPhone 3GS or newer devices. You should aim at 10K or less vertices visible per frame when targeting older devices equipped with MBX GPU, such as: iPhone, iPhone 3G, iPod Touch 1st and 2nd Generation.

Lighting Performance

Per-pixel dynamic lighting will add significant cost to every affected pixel and can lead to rendering object in multiple passes. Avoid having more than one Pixel Light affecting any single object, prefer it to be a directional light. Note that Pixel Light is a light which has a Render Mode setting set to Important.

Per-vertex dynamic lighting can add significant cost to vertex transformations. Avoid multiple lights affecting single objects. Bake lighting for static objects.

Optimize Model Geometry

When optimizing the geometry of a model, there are two basic rules:

  • Don't use excessive amount of faces if you don't have to
  • Keep the number of UV mapping seams and hard edges as low as possible

Note that the actual number of vertices that graphics hardware has to process is usually not the same as what is displayed in a 3D application. Modeling applications usually display the geometric vertex count, i.e. number of points that make up a model.

For a graphics card however, some vertices have to be split into separate ones. If a vertex has multiple normals (it's on a "hard edge"), or has multiple UV coordinates, or has multiple vertex colors, it has to be split. So the vertex count you see in Unity is almost always different from the one displayed in 3D application.

Texture Compression

Use iOS native PVRT compression formats. They will not only decrease the size of your textures (resulting in faster load times and smaller memory footprint), but also can dramatically increase your rendering performance! Compressed texture requires only a fraction of memory bandwidth compared to full blown 32bit RGBA textures. For performance comparison check iOS Hardware Guide.

Some images are prone to visual artifacts in alpha channels of PVRT compressed textures. In such case you might want to tweak PVRT compression parameters directly in your imaging software. You can do that by installing PVR export plugin or using PVRTexTool from Imagination Tech -- creators of PVRT format. Resulting compressed image with .pvr extension will be imported by Unity Editor as is and manually specified compression parameters will be preserved.

If PVRT compression formats do not deliver enough visual quality and you need extra crisp imaging (for example UI textures), then you should consider using 16bit texture over full 32bit RGBA texture. At least you will reduce memory bandwidth by half.

Tips for writing well performing shaders

Although GPUs fully support pixel and vertex shaders since iPhone 3GS, do not expect to grab a desktop shader with complex per-pixel functionality and run it on iOS device at 30 frames per second. Most often shaders will have to be hand optimized, calculations and texture reads kept to a minimum in order to achieve good frame rates.

Complex arithmetic operations

Arithmetic operations such as pow, exp, log, cos, sin, tan etc heavily tax GPU. Rule of thumb is to have not more than one such operation per fragment. Consider that sometimes lookup textures could be a better alternative.

Do NOT try to roll your own normalize, dot, inversesqrt operations however. Always use built-in ones -- this was driver will generate much better code for you.

Keep in mind that discard operation will make your fragments slower.

Floating point operations

Always specify precision of the floating point variables while writing custom shaders. It is crucial to pick smallest possible format in order to achieve best performance.

If shader is written in GLSL ES, then precision is specified as following:

  • highp - full 32 bits floating point format, well suitable for vertex transformations, slowest
  • mediump - reduced 16 bits floating point format, well suitable for texture UV coordinates, roughly x2 faster than highp
  • lowp - 10 bits fixed point format, well suitable for colors, lighting calculation and other high performant operations, roughly x4 faster than highp

If shader is written in CG or it is a surface shader, then precision is specified as following:

  • float - analogous to highp in GLSL ES, slowest
  • half - analogous to mediump in GLSL ES, roughly x2 faster than float
  • fixed - analogous to lowp in GLSL ES, roughly x4 faster than float

For more details about general shader performance, please read the Shader Performance page.

Hardware documentation

Take your time to study Apple documentations on hardware and best practices for writing shaders. Note that we would suggest to be more aggressive with floating point precision hints however.

Bake Lighting into Lightmaps

Bake your scene static lighting into textures using Unity built-in Lightmapper. The process of generating a lightmapped environment takes only a little longer than just placing a light in the scene in Unity, but:

  • It is going to run a lot faster (2-3 times for eg. 2 pixel lights)
  • And look a lot better since you can bake global illumination and the lightmapper can smooth the results

Share Materials

If a number of objects being rendered by the same camera uses the same material, then Unity iOS will be able to employ a large variety of internal optimizations such as:

  • Avoiding setting various render states to OpenGL ES.
  • Avoiding calculation of different parameters required to setup vertex and pixel processing
  • Batching small moving objects to reduce draw calls
  • Batching both big and small objects with enabled "static" property to reduce draw calls

All these optimizations will save you precious CPU cycles. Therefore, putting extra work to combine textures into single atlas and making number of objects to use the same material will always pay off. Do it!

Simple Checklist to make Your Game Faster

  • Keep vertex count below:
    • 40K per frame when targeting iPhone 3GS and newer devices (with SGX GPU)
    • 10K per frame when targeting older devices (with MBX GPU)
  • If you're using built-in shaders, peek ones from Mobile category. Keep in mind that Mobile/VertexLit is currently the fastest shader.
  • Keep the number of different materials per scene low - share as many materials between different objects as possible.
  • Set Static property on a non-moving objects to allow internal optimizations.
  • Use PVRTC formats for textures when possible, otherwise choose 16bit textures over 32bit.
  • Use combiners or pixel shaders to mix several textures per fragment instead of multi-pass approach.
  • If writing custom shaders, always use smallest possible floating point format:
    • fixed / lowp -- perfect for color, lighting information and normals,
    • half / mediump -- for texture UV coordinates,
    • float / highp -- avoid in pixel shaders, fine to use in vertex shader for vertex position calculations.
  • Minimize use of complex mathematical operations such as pow, sin, cos etc in pixel shaders.
  • Do not use Pixel Lights when it is not necessary -- choose to have only a single (preferably directional) pixel light affecting your geometry.
  • Do not use dynamic lights when it is not necessary -- choose to bake lighting instead.
  • Choose to use less textures per fragment.
  • Avoid alpha-testing, choose alpha-blending instead.
  • Do not use fog when it is not necessary.
  • Learn benefits of Occlusion culling and use it to reduce amount of visible geometry and draw-calls in case of complex static scenes with lots of occlusion. Plan your levels to benefit from Occlusion culling.
  • Use skyboxes to "fake" distant geometry.

See Also

Android

Lighting Performance

Per-pixel dynamic lighting will add significant cost to every affected pixel and can lead to rendering object in multiple passes. Avoid having more than one Pixel Light affecting any single object, prefer it to be a directional light. Note that Pixel Light is a light which has a Render Mode setting set to Important.

Per-vertex dynamic lighting can add significant cost to vertex transformations. Avoid multiple lights affecting single objects. Bake lighting for static objects.

Optimize Model Geometry

When optimizing the geometry of a model, there are two basic rules:

  • Don't use excessive amount of faces if you don't have to
  • Keep the number of UV mapping seams and hard edges as low as possible

Note that the actual number of vertices that graphics hardware has to process is usually not the same as what is displayed in a 3D application. Modeling applications usually display the geometric vertex count, i.e. number of points that make up a model.

For a graphics card however, some vertices have to be split into separate ones. If a vertex has multiple normals (it's on a "hard edge"), or has multiple UV coordinates, or has multiple vertex colors, it has to be split. So the vertex count you see in Unity is almost always different from the one displayed in 3D application.

Texture Compression

All Android devices with support for OpenGL ES 2.0 also support the ETC1 compression format; it's therefore encouraged to whenever possible use ETC1 as the prefered texture format. Using compressed textures is important not only to decrease the size of your textures (resulting in faster load times and smaller memory footprint), but can also increase your rendering performance dramatically! Compressed textures require only a fraction of memory bandwidth compared to full blown 32bit RGBA textures.

If targeting a specific graphics architecture, such as the Nvidia Tegra or Qualcomm Snapdragon, it may be worth considering using the proprietary compression formats available on those architectures. The Android Market also allows filtering based on supported texture compression format, meaning a distribution archive (.apk) with for example DXT compressed textures can be prevented for download on a device which doesn't support it.

Enable Mip Maps

As a rule of thumb, always have Generate Mip Maps enabled. In the same way Texture Compression can help limit the amount of texture data transfered when the GPU is rendering, a mip mapped texture will enable the GPU to use a lower-resolution texture for smaller triangles. The only exception to this rule is when a texel (texture pixel) is known to map 1:1 to the rendered screen pixel, as with UI elements or in a pure 2D game.

Tips for writing well performing shaders

Although all Android OpenGL ES 2.0 GPUs fully support pixel and vertex shaders, do not expect to grab a desktop shader with complex per-pixel functionality and run it on Android device at 30 frames per second. Most often shaders will have to be hand optimized, calculations and texture reads kept to a minimum in order to achieve good frame rates.

Complex arithmetic operations

Arithmetic operations such as pow, exp, log, cos, sin, tan etc heavily tax GPU. Rule of thumb is to have not more than one such operation per fragment. Consider that sometimes lookup textures could be a better alternative.

Do NOT try to roll your own normalize, dot, inversesqrt operations however. Always use built-in ones -- this was driver will generate much better code for you.

Keep in mind that discard operation will make your fragments slower.

Floating point operations

Always specify precision of the floating point variables while writing custom shaders. It is crucial to pick smallest possible format in order to achieve best performance.

If shader is written in GLSL ES, then precision is specified as following:

  • highp - full 32 bits floating point format, well suitable for vertex transformations, slowest
  • mediump - reduced 16 bits floating point format, well suitable for texture UV coordinates, roughly x2 faster than highp
  • lowp - 10 bits fixed point format, well suitable for colors, lighting calculation and other high performant operations, roughly x4 faster than highp

If shader is written in CG or it is a surface shader, then precision is specified as following:

  • float - analogous to highp in GLSL ES, slowest
  • half - analogous to mediump in GLSL ES, roughly x2 faster than float
  • fixed - analogous to lowp in GLSL ES, roughly x4 faster than float

For more details about general shader performance, please read the Shader Performance page. Quoted performance figures are based on the PowerVR graphics architecture, available in devices such as the Samsung Nexus S. Other hardware architectures may experience less (or more) benefit from using reduced register precision.

Bake Lighting into Lightmaps

Bake your scene static lighting into textures using Unity built-in Lightmapper. The process of generating a lightmapped environment takes only a little longer than just placing a light in the scene in Unity, but:

  • It is going to run a lot faster (2-3 times for eg. 2 pixel lights)
  • And look a lot better since you can bake global illumination and the lightmapper can smooth the results

Share Materials

If a number of objects being rendered by the same camera uses the same material, then Unity Android will be able to employ a large variety of internal optimizations such as:

  • Avoiding setting various render states to OpenGL ES.
  • Avoiding calculation of different parameters required to setup vertex and pixel processing
  • Batching small moving objects to reduce draw calls
  • Batching both big and small objects with enabled "static" property to reduce draw calls

All these optimizations will save you precious CPU cycles. Therefore, putting extra work to combine textures into single atlas and making number of objects to use the same material will always pay off. Do it!

Simple Checklist to make Your Game Faster

  • If you're using built-in shaders, peek ones from Mobile category. Keep in mind that Mobile/VertexLit is currently the fastest shader.
  • Keep the number of different materials per scene low - share as many materials between different objects as possible.
  • Set Static property on a non-moving objects to allow internal optimizations.
  • Use ETC1 format for textures when possible, otherwise choose 16bit textures over 32bit for uncompressed texture data.
  • Use mipmaps.
  • Use combiners or pixel shaders to mix several textures per fragment instead of multi-pass approach.
  • If writing custom shaders, always use smallest possible floating point format:
    • fixed / lowp -- perfect for color, lighting information and normals,
    • half / mediump -- for texture UV coordinates,
    • float / highp -- avoid in pixel shaders, fine to use in vertex shader for vertex position calculations.
  • Minimize use of complex mathematical operations such as pow, sin, cos etc in pixel shaders.
  • Do not use Pixel Lights when it is not necessary -- choose to have only a single (preferably directional) pixel light affecting your geometry.
  • Do not use dynamic lights when it is not necessary -- choose to bake lighting instead.
  • Choose to use less textures per fragment.
  • Avoid alpha-testing, choose alpha-blending instead.
  • Do not use fog when it is not necessary.
  • Learn benefits of Occlusion culling and use it to reduce amount of visible geometry and draw-calls in case of complex static scenes with lots of occlusion. Plan your levels to benefit from Occlusion culling.
  • Use skyboxes to "fake" distant geometry.

See Also

Page last updated: 2011-05-24